package cn.lingyangwl.agile.food.core.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.lingyangwl.agile.common.security.utils.*;
import cn.lingyangwl.agile.food.core.assembly.*;
import cn.lingyangwl.agile.food.core.mapper.*;
import cn.lingyangwl.agile.food.core.model.rqrs.cart.*;
import cn.lingyangwl.agile.food.core.service.*;
import cn.lingyangwl.agile.food.model.constants.ShopCons.*;
import cn.lingyangwl.agile.food.model.entity.*;
import cn.lingyangwl.agile.food.model.rqrs.goods.*;
import cn.lingyangwl.agile.model.module.tenant.*;
import cn.lingyangwl.framework.core.utils.spring.*;
import cn.lingyangwl.framework.lock.manager.*;
import cn.lingyangwl.framework.lock.model.*;
import cn.lingyangwl.framework.tool.core.exception.*;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.service.IService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 购物车
 *
 * @author shenguangyang
 */
@Slf4j
@Service
public class CartServiceImpl extends ServiceImpl<CartMapper, Cart>
    implements IService<Cart>, CartService {
    @Resource
    private CartAssembly cartAssembly;
    @Resource
    private GoodsService goodsService;
    @Resource
    private GoodsAssembly goodsAssembly;
    @Resource
    private GoodsSkuService goodsSkuService;
    @Resource
    private StoreService storeService;
    @Resource
    private GoodsAttributeService attributeService;
    @Resource
    private ILockManager lockManager;
    
    @Override
    public CartResp listCart(CartListReq req) {
        CartResp resp = new CartResp();
        CartResp.Info info = new CartResp.Info();
        resp.setCartInfo(info);

        LambdaQueryWrapper<Cart> lqw = Cart.lqw()
            .eq(Cart::getUserId, SecurityUtils.getUserId())
            .in(CollUtil.isNotEmpty(req.getCartIds()), Cart::getId, req.getCartIds())
            .eq(Objects.nonNull(req.getTableId()), Cart::getTableId, req.getTableId())
            .eq(Cart::getCartType, req.getCartType());
        List<Cart> carts = this.list(lqw);

        // 过滤并删除购物数量为0的
        List<Cart> deletedCard = carts.stream().filter(e -> e.getGoodsNum().compareTo(0) <= 0).collect(Collectors.toList());
        deletedCard.forEach(carts::remove);
        if (CollUtil.isNotEmpty(deletedCard)) {
            this.removeByIds(deletedCard.stream().map(Cart::getId).collect(Collectors.toList()));
        }
        if (CollUtil.isEmpty(carts)) {
            return resp;
        }

        // 获取商品属性id
        List<Long> attributeIds = carts.stream()
            .flatMap(e -> e.getAttributeList().stream()).map(Cart.Attribute::getId)
            .collect(Collectors.toList());
        // 对属性进行加工 key = id + 属性名称, value: 附加价格
        List<GoodsAttribute> attributeList = attributeService.listAttributeByIds(attributeIds);
        Map<String, GoodsAttribute.Values> attributeMap = new HashMap<>();
        attributeList.forEach(e -> {
            Map<String, GoodsAttribute.Values> tempMap = e.getValues().stream()
                .collect(Collectors.toMap(e1 -> e.getId() + "_" + e1.getName(), Function.identity()));
            attributeMap.putAll(tempMap);
        });

        Map<Long, List<Cart>> cartMap = carts.stream().collect(Collectors.groupingBy(Cart::getGoodsId));
        List<Long> goodsIds = carts.stream().map(Cart::getGoodsId).collect(Collectors.toList());

        // 查找规格
        List<GoodsSku> skuList = goodsSkuService.listSkuByGoodsIds(goodsIds);
        Map<Long, GoodsSku> skuMap = skuList.stream().collect(Collectors.toMap(GoodsSku::getId, Function.identity()));
        if (CollUtil.isEmpty(skuList)) {
            throw new BizException("商品规格信息已修改, 请重新选择");
        }

        // step 处理购物车中的商品
        List<CartResp.Goods> goodsList = goodsService.listByIds(goodsIds).stream().map(goodsAssembly::toCartGoodsResp)
            .filter(e -> cartMap.containsKey(e.getId()))
            .flatMap(e -> {
                List<Cart> cartList = cartMap.get(e.getId());
                return cartList.stream().map(cart -> {
                    CartResp.Goods goods = cartAssembly.copy(e);
                    GoodsSku goodsSku = skuMap.get(cart.getGoodsSkuId());
                    goods.setBuyNum(cart.getGoodsNum());

                    // 计算属性价钱
                    BigDecimal attrPrice = cart.getAttributeList().stream()
                        .flatMap(e1 -> e1.flatKeys().stream())
                        .filter(attributeMap::containsKey)
                        .map(attributeMap::get).map(GoodsAttribute.Values::getAdditionalPrice)
                        .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
                    goods.setAttrAdditionalPrice(attrPrice);

                    goods.setDescribe(cart.getDescribe())
                        .setCartId(cart.getId())
                        .setPrice(goodsSku.getPrice()).setCostPrice(goodsSku.getCostPrice());
                    goods.setImage(goods.getImages().stream().findFirst().orElse(null));

                    // 设置划线价钱
                    BigDecimal linePrice = Optional.ofNullable(goods.getLinePrice()).orElse(BigDecimal.ZERO);
                    goods.setLinePrice(linePrice.add(attrPrice));

                    return goods;
                });
            }).collect(Collectors.toList());

        if (CollUtil.isEmpty(goodsList)) {
            throw new BizException("商品已下架");
        }

        // step 计算包装费
        boolean isBag = DeliveryWay.isBag(req.getDeliveryWay());
        BigDecimal totalBagPrice = isBag ? getTotalBagPrice(req.getCartType(), carts) : BigDecimal.ZERO;

        // step 计算总价
        BigDecimal totalGoodsPrice = goodsList.stream()
            .map(e -> {
                BigDecimal buyNum = BigDecimal.valueOf(e.getBuyNum());
                BigDecimal goodsPrice = e.getPrice().multiply(buyNum);
                return goodsPrice.add(e.getAttrAdditionalPrice().multiply(buyNum));
            }).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);

        // step 计算总的划线价钱
        BigDecimal totalLinePrice = goodsList.stream().map(GoodsBase::getLinePrice).reduce(BigDecimal::add).orElse(BigDecimal.ZERO);

        info.setCartTotalNum(carts.size());
        info.setTotalGoodsPrice(totalGoodsPrice);
        info.setTotalBagPrice(totalBagPrice);
        info.setTotalLinePrice(totalLinePrice);

        resp.setCartInfo(info);
        resp.setGoodsList(goodsList);
        return resp;
    }

    private BigDecimal getTotalBagPrice(String cartType, List<Cart> carts) {
        ShopStore store = storeService.getById(TenantContext.get().getTenantId());
        if (Objects.isNull(store)) {
            throw new BizException("当前门店不存在, 请重新进入");
        }

        BigDecimal totalBagPrice;
        if (GoodsType.TAKE_IN.equals(cartType)) {
            String storeBagType = store.getStoreBagType();
            if (BagType.GOODS.equals(storeBagType)) {
                totalBagPrice = carts.stream()
                    .map(e -> e.getBagPrice().multiply(BigDecimal.valueOf(e.getGoodsNum())))
                    .reduce(BigDecimal::add).orElse(BigDecimal.ZERO);
            } else if (BagType.ORDER.equals(storeBagType)) {
                totalBagPrice = store.getStoreBagPrice();
            } else {
                throw new BizException("未知的包装费类型, 请联系商家");
            }
        } else {
            throw new BizException("暂时不支持的购物车类型");
        }
        return totalBagPrice;
    }

    @Override
    public CartResp saveCart(CartSaveReq req) {
        Goods goods = goodsService.getById(req.getGoodsId());
        if (Objects.isNull(goods)) {
            throw new BizException("商品不存在");
        }
        Cart entity = cartAssembly.toCart(req);
        entity.genCartNo();
        LambdaQueryWrapper<Cart> lqw = Cart.lqw()
            .eq(Cart::getCartType, req.getCartType()).eq(Cart::getCartNo, entity.getCartNo())
            .eq(Cart::getGoodsSkuId, req.getGoodsSkuId())
            .eq(Cart::getGoodsId, req.getGoodsId());
        Cart cart = this.getOne(lqw);
        if (Objects.nonNull(cart)) {
            CartUpdateReq updateReq = new CartUpdateReq();
            updateReq.setNumber(1);
            updateReq.setGoodsId(req.getGoodsId());
            updateReq.setId(cart.getId());
            updateReq.setCartType(req.getCartType());
            CartServiceImpl aopProxy = SpringUtils.getAopProxy(this);
            return aopProxy.changeNum(updateReq);
        } else {
            entity.setMultiSpec(goods.getMultiSpec());
            this.save(entity);
            return this.listCart(new CartListReq().setCartType(req.getCartType()));
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public CartResp changeNum(CartUpdateReq req) {
        ILock lock = lockManager.getLock(String.format("lock:cart:%s", SecurityUtils.getUserId()));
        lock.lock();
        try {
            // 购物车id, 只有当是单规格时, 才会不传
            LambdaUpdateWrapper<Cart> luw = Cart.luw()
                .eq(Objects.nonNull(req.getId()), Cart::getId, req.getId())
                .eq(Cart::getGoodsId, req.getGoodsId())
                .eq(Cart::getCartType, req.getCartType())
                .setSql(String.format("goods_num = goods_num + %s", req.getNumber()));
            this.update(luw);

            CartListReq listReq = new CartListReq();
            listReq.setCartType(req.getCartType());
            return this.listCart(listReq);
        } finally {
            lock.unlock();
        }
    }
    
    @Override
    public boolean batchDeleteCart(List<Long> ids,  String cartType) {
        if (CollUtil.isEmpty(ids)) {
            return false;
        }
        LambdaQueryWrapper<Cart> lqw = Cart.lqw()
            .eq(Cart::getUserId, SecurityUtils.getUserId()).in(Cart::getId, ids)
            .eq(Cart::getCartType, cartType);
    	return this.remove(lqw);
    }
}
